iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 18
1
AI & Data

D3.js資料視覺化的浪漫突進系列 第 18

Day18 D3js d3.path麻煩的線段路徑

  • 分享至 

  • xImage
  •  

D3js d3.path

用途

d3.path用途就是用來繪製複雜的path路徑。d3.path會回傳一個實現canvas路徑的物件並序列化成svg路徑資料。

Canvas API

範例:

let canvas = document.getElementById('target');
let context = canvas.getContext('2d');

function draw(context) {
  context.moveTo(10, 10); // 移動到(10, 10)
  context.lineTo(100, 10); // 連線至(100, 10)
  context.arcTo(150, 150, 300, 10, 40); // 繪製一弧形。
  context.lineTo(300, 10); // 連線至 ⟨300,10⟩
  return context;
}

context.beginPath();
draw(context);
context.stroke();

d3.path

而如果我們將d3.path放入function draw,根據敘述,d3.path就是支援canvas context的繪製方法。

console.log(draw(d3.path()).toString());

符合期待,d3.path是支援canvas繪製path的方法,並會序列化成svg路徑並。

既然已經有辦法產生路徑,試試看在svg上吧!

d3.select("svg")
  .call(svg => svg.append("path")
    .style("stroke", "black")
    .style("fill", "none")
    .attr("d", draw(d3.path())))
  .node()

canvas結果相同!

path的漸變

path針對資料變化產生的漸變有些特別,我們先來一個範例。

範例


// 宣告基本sv圖層與設定
const width = 1200;
const height = 600;
const padding = 30;
const innerWidth = width - padding;
const innerHeight = height - padding;
const svg = d3.select('svg').attr('width', width).attr('height', height);
const rootLayer = svg.append('g').attr('transform', `translate(${padding}, ${padding})`)
const axisLayer = rootLayer.append('g');
const xAxisLayer = axisLayer.append('g');
const yAxisLayer = axisLayer.append('g');
const lineLayer = rootLayer.append('g');
let path = lineLayer.append('path');

// 資料源
let datas = []

// 繪製path的部分
const drawLine = (ctx, datas, xScale, yScale) => {
  datas.forEach((data, index) => {
   // 如果是0,代表是第一個資料,不需要LINETO
    if (index !== 0) {
      ctx.lineTo(xScale(data.time), yScale(data.value));    
    }
    ctx.moveTo(xScale(data.time), yScale(data.value))
  });
  return ctx
}

// 繪製方法

const draw = (datas) => {
  let xExtent = d3.extent(datas, data => new Date(data.time));
  let yExtent = d3.extent(datas, data => data.value);
  let xScale = d3.scaleTime().range([0, innerWidth]).domain(xExtent);
  let yScale = d3.scaleLinear().range([0, innerHeight]).domain(yExtent);
  let xAxis = d3.axisBottom(xScale).tickFormat( d3.timeFormat("%H:%M:%S"));
  let yAxis = d3.axisLeft(yScale);
  xAxisLayer.call(xAxis);
  yAxisLayer.call(yAxis);
  
  // 產生PATH路徑的部分
  let linePath = drawLine(d3.path(), datas, xScale, yScale);
  
  // 將產生的路徑設定置PATH元件上。
  path
    .attr('fill', 'none')
    .attr('stroke', 'black')
    .transition()
    .attr('d', linePath.toString());

}

// 自動產生一堆資料
const autoGen = () => {
  datas.push({
    value: Math.round(Math.random() * 100),
    time: new Date()
  })
}


setInterval(() => {
  autoGen();
  // 超過20筆資料就去掉第一個
  if (datas.length > 20) {
    datas = datas.slice(1)
  }
  draw(datas)
}, 200);

有發現問題,與之前d3.selection data的問題一樣,其實線段應該要水平移動,並非垂直更動數值,但由於目前已經產生為line path一串路徑字串,其實d3確實沒辦法知道到底該如何變化。

至於說要如何克服此問題,就必須透過其他平移技巧。

結論

不想自己寫繪製部分,還可以使用d3.line,不過可以了解pathcanvas的繪製方法,不覺得挺有趣的嗎?

Codepen範例

參考

d3-path
Mike post


上一篇
Day17 D3js d3.brush與d3.zoom實現選取放大功能
下一篇
Day19 D3js d3.arc, d3.pie 來個圓餅圖
系列文
D3.js資料視覺化的浪漫突進30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言